%%% File    : gate_sup.erl
%%% Author  :  <>
%%% Description : 
%%% Created : 23 Mar 2011 by  <>
%%one machine can only start a net_server
-module(netser_sup).

-export([start_link/0]).

-export([start_net/1, start_net/2, stop_net/2]).

-export([add_extra/1, del_extra/1]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	terminate/2, code_change/3]).

-define(Port, 6666).

-define(SERVER, ?MODULE).

-record(state, {type,
	       net,
	       restart,
	       extra}).

%%----------
start_net(Node) when is_atom(Node) ->
    start_net(Node, ?Port);
start_net(_) ->
    {error, badarg}.

start_net(Node, Port) ->
    gen_server:call({global, ?SERVER}, {start_net, Node, Port}).

stop_net(Node, Port) ->
    gen_server:call({global, ?SERVER}, {stop_net, Node, Port}).
  
add_extra(Node) when is_atom(Node) ->
    gen_server:call({global, ?SERVER}, {add_extra, Node});
add_extra(_) ->
    {error, badarg}.

del_extra(Node) when is_atom(Node) ->
    gen_server:call({global, ?SERVER}, {del_extra, Node});
del_extra(_) ->
    {error, badarg}.

start_link() ->
    gen_server:start_link({global, ?SERVER}, ?MODULE, [], []).

%%--------
init([]) ->
    process_flag(trap_exit, true),
    pg2:create(net),
    S = #state{type=net,
	      net=[], %%[node,port,pid]
	      restart=3,
	      extra=[]},
    {ok, S}.

handle_call({add_extra, Node}, _From, S=#state{extra=Extra}) ->
    case lists:member(Node, Extra) of
	true ->
	    {reply, {error, exist}, S};
	false ->
	    {reply, ok, S#state{extra=[Node|Extra]}}
    end;

handle_call({del_extra, Node}, _From, S=#state{extra=Extra}) ->
    case lists:member(Node, Extra) of
	false ->
	    {reply, {error, noexist}, S};
	true ->
	    {reply, S#state{extra=lists:delete(Node, Extra)}}
    end;
    
handle_call({start_net, Node, Port}, _From, S) ->
    {Res, Ns} = do_start({Node, Port}, S),
    {reply, Res, Ns};

handle_call({stop_net, Node, Port}, _From, S) ->
    {Res, Ns} = do_stop({Node, Port}, S),
    {reply, Res, Ns};

handle_call(_Req, _From, S) ->
    {reply, ok, S}.

handle_cast({redirect, Node, Port, Pid}, S=#state{net=Net}) ->
    link(Pid),
    %%pg2:join(net, Pid),
    {noreply, S#state{net=[{Node, Port, Pid}|Net]}};

handle_cast(_Msg, S) ->
    {noreply, S}.

handle_info({'EXIT', _, normal}, S) ->
    {noreply, S};

handle_info({'EXIT', Pid, _Reason}, S=#state{net=Net}) ->
    case lists:keysearch(Pid, 3, Net) of
	false ->
	    {noreply, S};
	{value, {Node, Port, Pid}} ->
	    Ns = do_restart(Node, Port, S),
	    {noreply, Ns}
    end;

handle_info(_Info, S) ->
    {noreply, S}.

terminate(_Reason, _S) -> 
    ok.

code_change(_Oldvsn, S, _Extra) ->
    {ok, S}.
  
%%----------
do_start({Node, Port}, S=#state{net=Net}) ->
    case lists:keymember(Node, 1, Net) of
	true ->
	    {{error, exist_one_netserver}, S};
	false ->
	    Res = rpc:call(Node, net_server, start, [self(), Port]),
	    case Res of
		{ok, Pid} ->
		    link(Pid),
		    pg2:join(net, Pid),
		    {ok, S#state{net=[{Node, Port, Pid}|Net]}};
		Err ->
		    {{error, Err}, S}
            end
    end.

do_stop({Node, Port}, S=#state{net=Net}) ->
    case lists:keysearch(Node, 1, Net) of
	{value, {Node, Port, Pid}} ->
	    do_exit(Pid),
	    {ok, S#state{net=lists:keydelete(Node, 1, Net)}};
	{value, {Node, _, _Pid}} ->
	    {{error, noportexist}, S};
	false ->
	    {{error, noexist}, S}
    end.

do_exit(Pid) ->
    gen_server:cast(Pid, {do_exit, self()}).

		    
do_restart(Node, Port, S=#state{restart=Restart}) ->
    do_restart(Node, Port, S, Restart).

do_restart(Node, Port, S=#state{extra=Extra}, 0) ->
    start_one_extra(Extra, S, Node, Port);
do_restart(Node, Port, S=#state{net=Net}, Restart) ->
    Res = rpc:call(Node, net_server, start, [self(), Port]),
    case Res of
	{ok, Pid} ->
	    link(Pid),
	    pg2:join(net, Pid),
	    S#state{net=[{Node, Port, Pid}|lists:keydelete(Node, 1, Net)]};
	_Err ->
	    do_restart(Node, Port, S, Restart-1)
    end.

start_one_extra([], S=#state{net=Net}, Node, _Port) ->
    S#state{net=lists:keydelete(Node, 1, Net)};
start_one_extra([H|T], S=#state{net=Net, extra=Extra}, Node, Port) ->
    Res = rpc:call(H, net_server, start, [self(), Port]),
    case Res of
	{ok, Pid} ->
	    link(Pid),
	    pg2:join(net, Pid),
	    S#state{net=[{H, Port, Pid}|lists:keydelete(Node, 1, Net)],
		   extra=lists:delete(H, Extra)};
	_Err ->
	    start_one_extra(T, S#state{extra=lists:delete(H, Extra)}, Node, Port)
    end.
